﻿using System.Collections.Generic;
using System.Collections.ObjectModel;
using Windows.Devices.Input;
using Windows.System;
using Windows.UI.Core;

namespace DXBase
{
	// http://blogs.msdn.com/b/windowsappdev/archive/2012/07/02/modernizing-input-in-windows-8.aspx
	/// <summary>
	/// This is an input controller that deliver event in a polling fashion (to be process by event loop).
	/// It needs to have a CoreWindow set to start saving events.
	/// </summary>
	public class InputController : ModelBase
	{
		public void Clear()
		{
			pointer_current = new Dictionary<uint, PointerEventArgs>();
			pointer_drag = new Dictionary<uint, Trail>();
			wheel = 0;
		}

		#region GetKeyStates()

		public Dictionary<VirtualKey, CoreVirtualKeyStates> GetKeyStates(params VirtualKey[] keys)
		{
			if (keys == null)
				return null;
			var result = new Dictionary<VirtualKey, CoreVirtualKeyStates>(keys.Length);
			foreach (var k in keys)
				result[k] = window == null ? CoreVirtualKeyStates.None : window.GetAsyncKeyState(k);
			return result;
		}

		#endregion

		#region Wheel

		/// <summary>
		/// This is modified by every wheel event
		/// </summary>
		public int WheelValue
		{
			get { return wheel; }
			set 
			{
				if (value == wheel)
					return;
				wheel = value;
				OnPropertyChanged();
			}
		}
		private int wheel;

		#endregion

		#region Cursor

		public CoreCursor Cursor
		{
			get
			{
				if (window == null)
					return null;
				return window.PointerCursor;
			}
			set
			{
				if (value == Cursor)
					return;
				if (window != null)
					window.PointerCursor = value;
				OnPropertyChanged();
			}
		}

		#endregion

		#region CoreWindow and mouse states: GetCurrentPoints(), GetTrails()

		// user can filter this list by PointerDeviceType
		public IEnumerable<PointerEventArgs> GetCurrentPoints() { return pointer_current.Values; }
		/// <summary>
		/// For each tracked device contain the latest pointer down and dragged points.
		/// </summary>
		public IEnumerable<Trail> GetTrails() { return pointer_drag.Values; }

		public Trail GetMouseTrailOrOther()
		{
			Trail result = null;
			foreach (var trail in GetTrails())
			{
				if (trail.DownPoint.CurrentPoint.PointerDevice.PointerDeviceType == PointerDeviceType.Mouse)
					return trail;
				if (result == null)
					result = trail;
			}
			return result;
		}

		/// <summary>
		/// Call <see cref="Trail.Trim()"/> on all trails. <seealso cref="GetTrails()"/>
		/// </summary>
		public void TrimTrails()
		{
			foreach (var item in GetTrails())
				item.Trim();
		}

		/// <summary>
		/// This the core window, one must be set to track events.
		/// </summary>
		public CoreWindow CoreWindow 
		{
			get { return window; }
			set
			{
				if (value == window)
					return;

				if (window != null)
					UnegisterEvents();
				window = value;
				if (window != null)
					RegisterEvents();
				Clear();
				OnPropertyChanged();
			}
		}
		CoreWindow window;

		void RegisterEvents()
		{
			window.PointerPressed += OnPointerPressed;
			window.PointerMoved += OnPointerMoved;
			window.PointerReleased += OnPointerReleased;
			window.PointerWheelChanged += OnPointerWheelChanged;
		}

		void UnegisterEvents()
		{
			window.PointerPressed -= OnPointerPressed;
			window.PointerMoved -= OnPointerMoved;
			window.PointerReleased -= OnPointerReleased;
			window.PointerWheelChanged -= OnPointerWheelChanged;
		}

		/// <summary>
		///  All the point for the last pointer down events.
		/// </summary>
		public class Trail
		{
			internal Trail()
			{
				Pressed = true;
				points = new List<PointerEventArgs>();
				ropoints = new ReadOnlyCollection<PointerEventArgs>(points);
			}
			internal List<PointerEventArgs> points;
			ReadOnlyCollection<PointerEventArgs> ropoints;

			public bool Handled { get; set; }
			public PointerEventArgs DownPoint { get; internal set; }
			public PointerEventArgs UpPoint { get; internal set; }

			public bool Pressed { get; internal set; }
			public IReadOnlyList<PointerEventArgs> Points { get { return ropoints; } }

			/// <summary>
			/// Remove all point but the last
			/// </summary>
			public void Trim()
			{
				if (points.Count > 1)
					points.RemoveRange(0, points.Count - 1);
			}
		}

		Dictionary<uint, PointerEventArgs> pointer_current = new Dictionary<uint, PointerEventArgs>();
		Dictionary<uint, Trail> pointer_drag = new Dictionary<uint, Trail>();

		void OnPointerReleased(CoreWindow sender, PointerEventArgs e)
		{
			pointer_current[e.CurrentPoint.PointerId] = e;
			Trail trail;
			if (pointer_drag.TryGetValue(e.CurrentPoint.PointerId, out trail))
			{
				trail.points.Add(e);
				trail.Pressed = false;
				trail.UpPoint = e;
			}
		}
		void OnPointerMoved(CoreWindow sender, PointerEventArgs e)
		{
			pointer_current[e.CurrentPoint.PointerId] = e;
			Trail trail;
			if (pointer_drag.TryGetValue(e.CurrentPoint.PointerId, out trail) && trail.Pressed)
				trail.points.Add(e);
		}
		void OnPointerPressed(CoreWindow sender, PointerEventArgs e)
		{
			pointer_current[e.CurrentPoint.PointerId] = e;
			pointer_drag[e.CurrentPoint.PointerId] = new Trail { points = { e }, DownPoint = e };
		}
		void OnPointerWheelChanged(CoreWindow sender, PointerEventArgs e)
		{
			pointer_current[e.CurrentPoint.PointerId] = e;
			WheelValue += e.CurrentPoint.Properties.MouseWheelDelta;
		}

		#endregion
	}
}
